-
Notifications
You must be signed in to change notification settings - Fork 8k
feat(esp_driver_uart): Add uart_write_bytes_with_timeout() (IDFGH-16708) #17798
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
👋 Hello jimmyw, we appreciate your contribution to this project! 📘 Please review the project's Contributions Guide for key guidelines on code, documentation, testing, and more. 🖊️ Please also make sure you have read and signed the Contributor License Agreement for this project. Click to see more instructions ...
Review and merge process you can expect ...
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the final PR Bugbot will review for you during this billing cycle
Your free Bugbot reviews will reset on November 20
Details
Your team is on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle for each member of your team.
To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.
9914847 to
bcb8e15
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the final PR Bugbot will review for you during this billing cycle
Your free Bugbot reviews will reset on December 20
Details
Your team is on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle for each member of your team.
To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.
bcb8e15 to
80046b1
Compare
| xRingbufferSendComplete(p_uart_obj[uart_num]->tx_ring_buf, tx_data); | ||
|
|
||
| // We checked the available space before, so the acquire should always succeed here | ||
| xRingbufferSend(p_uart_obj[uart_num]->tx_ring_buf, (void *)(src + offset), send_size, portMAX_DELAY); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Timeout not respected for data chunk transmission
The buffered TX path uses portMAX_DELAY when sending data chunks to the ring buffer, ignoring the user-specified ticks_to_wait. If the ring buffer unexpectedly blocks (due to a race condition or implementation bug), the function hangs forever instead of timing out as the API promises. This makes uart_write_bytes_with_timeout() unable to guarantee timeout behavior when using a TX buffer.
a971748 to
4eb65e6
Compare
This makes sure we wont break between header and data, fixes a proposed spinlock, and simplifies locking.
4eb65e6 to
2d7d921
Compare
Avoids locking if the uart is blocked due to flow control.
2d7d921 to
7af818b
Compare
|
Hi @jimmyw, Thanks for raising the PR! Is there a specific reason why you want to change the way of data sending to atomic (header+payload) style? I am afraid the logic change is too big for the current UART driver. Also, the atomic (header+payload) way would take more space in the ring buffer, since every data chunk needs a header now. It is reasonable to introduce a new API to have the timeout parameter to allow the bytes writing to return when getting blocked. However, this can be achieved in a relatively simple way: by just changing the |
|
Hi, thanks for your reply. Yes, there is a very good reason for doing header + data in one atomic operation, and that is if you manage to fit a header in the buffer, and and not data (Due to timeout) the uart will get of and think that the next header you add is the data, as it expects a data after header. I also think the code is cleaner, and more understandable this way, and with less overhead as we do less locking. It wont take much more buffer space, every write is currently an header + data today, even if you only write 1 byte to the uart. Its only when you write very big chunks in the buffer, it will be split up in 4 parts just to improve buffer stale lock handling. you dont want to fill up the buffer with a single piece of data, as that will dma block the full messagebuffer, and a less prioritized process wont be able to feed it in time, causing a buffer under-run. I added now so if the buffer have less space free, then whats we like to add to the buffer, it will split that into two, having the first write non blocking, and the second part will obviously block until timeout. This is an optimization, that we can remove. I am all for alternative suggestions on how to solve this. Just to make clear what i am trying to do with this PR, i give you an example (not completely sure this will happen in practice, but we hit a similar scenario): lets say we run PPP over a modem with flow control, LWIP stack wants to write 1500bytes to the uart, but the modem is busy with other stuff, not having a timeout on uart write will make the complete LWIP stack hang until the modem says so. And when it says, the tcp timeout on what we tried to send to the modem has passed already, so that would eventally be discarded anyway. Adding a timeout, makes LWIP notice that package was not send, and it can go on with other stuff, retrying later or dropping the package. Today with a blocking operation, it will sitt there wait 4 ever. |
| uart_hal_ena_intr_mask(&(uart_context[uart_num].hal), UART_INTR_TX_BRK_DONE); | ||
| UART_EXIT_CRITICAL_ISR(&(uart_context[uart_num].spinlock)); | ||
| p_uart->tx_waiting_brk = 1; | ||
| p_uart->tx_brk_flg = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you set p_uart->tx_brk_flg back to 0 at here, then UART_INTR_TXFIFO_EMPTY interrupt won't be able to re-enable after finishing send the TX break?
| // Limit an item to a quarter of the ring buffer size, if we allow a full item size the ring buffer | ||
| // needs to completely empty before we can send another large item, which can cause stalls. | ||
| // also makes sure that we dont try to add an item larger than the ring buffer itself | ||
| acquire_size = MIN(acquire_size, xRingbufferGetMaxItemSize(p_uart_obj[uart_num]->tx_ring_buf) / 4); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| acquire_size = MIN(acquire_size, xRingbufferGetMaxItemSize(p_uart_obj[uart_num]->tx_ring_buf) / 4); | |
| size_t max_one_time_send_size = xRingbufferGetMaxItemSize(p_uart_obj[uart_num]->tx_ring_buf) / 4; | |
| acquire_size = MIN(acquire_size, max_one_time_send_size); |
|
@jimmyw You are right, header + data in one atomic operation is necessary if we want to add the timeout option. The PR generally LGTM, I could sync it to our internal repo, but I don't guarantee it will be accepted due to the large UART TX logic change involved. We need to do some internal discussion and heavy testings to ensure no breaking change will be introduced. |
|
Thank you @songruo, i also did not expect adding a timeout would need this complex change. But the flip coin is that.
Think these are good reasons you could bring with you. |
Description
Its easy to think that an write to an uart will always succeed eventually, but that not always the case. If you enable hardware flow control, and have someone on the receiving end, that is stubborn never to release the ReadyToReceive line.
In some cases this has caused a deadlock, when both devices are both sitting and waiting for the other to give up.
Note
Introduces
uart_write_bytes_with_timeout()and refactors TX handling to support timeouts and partial-byte reporting across FIFO and ring buffer paths.uart_write_bytes_with_timeout(uart_port_t, const void*, size_t, TickType_t)declared ininclude/driver/uart.hand implemented insrc/uart.c.uart_tx_all()now takesTickType_t ticks_to_wait, enforces timeouts ontx_mux, ring buffer acquire, FIFO semaphores, and break wait; returns bytes sent or-1on timeout.xRingbufferSendAcquire/xRingbufferSendCompletewith explicit description item, uses current free size to chunk writes, tracksbytes_transmitted.ticks_to_waitand supports partial sends.uart_write_bytes()anduart_write_bytes_with_break()updated to calluart_tx_all(..., portMAX_DELAY); newuart_write_bytes_with_timeout()forwards caller-specified timeout.Written by Cursor Bugbot for commit 6dd58b8. This will update automatically on new commits. Configure here.